In this notebook you can find some descriptive analysis of the energy consumption of the family and the forecasting of consumption for the future months. Indeed, the goal of the project is to “sell” the submetering devices showing to the customers how they can benefit from the data collected by the devices. So, on the one hand, we show which useful information they can obtain considering “real time data” and agregated data coming from the devices, on the other hand how we can forecast the energy consumption using past data.

Exploration of the Dataset

R Markdown settings and R packages

# We set the options for the Rmarkdown
knitr::opts_chunk$set( warning = FALSE)

# We load the libraries 
if(require("pacman")=="FALSE"){
  install.packages("pacman")
}
pacman::p_load(plotly, corrplot, lubridate, hms, Hmisc, imputeTS,rstudioapi,kknn,ggplot2, randomForest, dplyr, shiny, tidyr, ggplot2, caret, forecast)

Dataset structure

We look at the dataset structure.

data <- read.delim("household_power_consumption.txt",header = TRUE, sep=";")
head(data)

Data Cleanining

We put the variables in the right type.

data$Global_reactive_power = suppressWarnings(as.numeric(as.character(data$Global_reactive_power)))
data$Global_active_power = suppressWarnings(as.numeric(as.character(data$Global_active_power)))
data$Sub_metering_1 = suppressWarnings(as.numeric(as.character(data$Sub_metering_1)))
data$Sub_metering_2= suppressWarnings(as.numeric(as.character(data$Sub_metering_2)))
data$Global_intensity = suppressWarnings(as.numeric(as.character(data$Global_intensity)))
data$Voltage = suppressWarnings(as.numeric(as.character(data$Voltage)))

We change the unit of measure of the active power and reactive power in order to be the same of the submetering (watt-hour). We create a column with the date and the time joined. Finally we change the time in order to take in consideration the solar and legal hour.

#Change the unite measure
data$Global_active_power = data$Global_active_power * 1000 / 60
data$Global_reactive_power = data$Global_reactive_power * 1000 / 60

#create a column in the format strptime

data<-cbind(data,paste(data$Date,data$Time), stringsAsFactors=FALSE)
colnames(data)[10] <-"DateTime"
data<- data[,c(ncol(data), 1:(ncol(data)-1))]
data$DateTime <- strptime(data$DateTime, "%d/%m/%Y %H:%M:%S", tz="GMT")
data$Date <- NULL
data$Time <-NULL
#solar and legal hour

a = which(data$DateTime == as.POSIXct("2007-03-25 02:00:00", tz = "GMT") | data$DateTime == as.POSIXct( "2007-10-28 01:59:00", tz = "GMT")) 
data[a[1]:a[2],1] = data[a[1]:a[2],1]+3600

a = which(data$DateTime == as.POSIXct("2008-03-30 02:00:00", tz = "GMT") | data$DateTime ==as.POSIXct( "2008-10-26 01:59:00", tz = "GMT")) 
data[a[1]:a[2],1] = data[a[1]:a[2],1]+3600

a = which(data$DateTime == as.POSIXct("2009-03-29 02:00:00", tz = "GMT") | data$DateTime ==as.POSIXct( "2009-10-25 01:59:00", tz = "GMT")) 
data[a[1]:a[2],1] = data[a[1]:a[2],1]+3600

a = which(data$DateTime == as.POSIXct("2010-03-28 02:00:00", tz = "GMT") | data$DateTime ==as.POSIXct( "2010-10-31 01:59:00", tz = "GMT")) 
data[a[1]:a[2],1] = data[a[1]:a[2],1]+3600

# Recreate the date column and reorder the order of the columns. 
data$Date <- as.Date(data$DateTime)
data=data[,c(1,9,2:8)]

We look at some summary measures of the variables.

summary(data)
    DateTime                        Date            Global_active_power
 Min.   :2006-12-16 17:24:00   Min.   :2006-12-16   Min.   :  1.267    
 1st Qu.:2007-12-12 00:18:30   1st Qu.:2007-12-12   1st Qu.:  5.133    
 Median :2008-12-06 07:13:00   Median :2008-12-06   Median : 10.033    
 Mean   :2008-12-06 07:48:33   Mean   :2008-12-05   Mean   : 18.194    
 3rd Qu.:2009-12-01 14:07:30   3rd Qu.:2009-12-01   3rd Qu.: 25.467    
 Max.   :2010-11-26 21:02:00   Max.   :2010-11-26   Max.   :185.367    
                                                    NA's   :25979      
 Global_reactive_power    Voltage      Global_intensity Sub_metering_1  
 Min.   : 0.000        Min.   :223.2   Min.   : 0.200   Min.   : 0.000  
 1st Qu.: 0.800        1st Qu.:239.0   1st Qu.: 1.400   1st Qu.: 0.000  
 Median : 1.667        Median :241.0   Median : 2.600   Median : 0.000  
 Mean   : 2.062        Mean   :240.8   Mean   : 4.628   Mean   : 1.122  
 3rd Qu.: 3.233        3rd Qu.:242.9   3rd Qu.: 6.400   3rd Qu.: 0.000  
 Max.   :23.167        Max.   :254.2   Max.   :48.400   Max.   :88.000  
 NA's   :25979         NA's   :25979   NA's   :25979    NA's   :25979   
 Sub_metering_2   Sub_metering_3  
 Min.   : 0.000   Min.   : 0.000  
 1st Qu.: 0.000   1st Qu.: 0.000  
 Median : 0.000   Median : 1.000  
 Mean   : 1.299   Mean   : 6.458  
 3rd Qu.: 1.000   3rd Qu.:17.000  
 Max.   :80.000   Max.   :31.000  
 NA's   :25979    NA's   :25979   

The missing values are the same quantity for all the numerical variables. Morevore we hace checked they are missing in the same rows. We take a look at how they are distributed. In particuar at their daily quantity.

#create a table with the missing values 
DATA=data[which(is.na(data$Global_reactive_power) == TRUE),]
t = table(DATA$Date)
print(t)

2006-12-21 2006-12-30 2007-01-14 2007-01-28 2007-02-22 2007-03-25 2007-04-28 
         2          2          1          1          2          1       1359 
2007-04-29 2007-04-30 2007-06-01 2007-06-06 2007-06-09 2007-06-19 2007-06-29 
      1440        924          1          1         38          2          1 
2007-07-15 2007-07-22 2007-08-01 2007-08-24 2007-09-26 2007-10-23 2007-11-21 
       130          1         21          1          2          2          1 
2007-11-29 2007-12-17 2008-01-13 2008-02-02 2008-02-23 2008-03-24 2008-05-16 
         1          1          1          1          2          1          2 
2008-06-13 2008-07-13 2008-08-04 2008-08-31 2008-10-25 2008-11-10 2008-11-12 
         1          2          1          1         43          6          2 
2008-11-23 2008-12-10 2008-12-20 2009-01-14 2009-02-01 2009-02-14 2009-02-17 
         1         70          1          1         38          2         24 
2009-03-01 2009-03-16 2009-04-13 2009-05-10 2009-05-26 2009-06-13 2009-06-14 
         1          1          2          1          3       1350       1440 
2009-06-15 2009-07-10 2009-08-13 2009-09-13 2009-09-30 2009-10-11 2009-11-09 
       515          4        891          1          2          1          1 
2009-12-10 2010-01-02 2010-01-12 2010-01-13 2010-01-14 2010-01-23 2010-02-10 
         2          1        547       1440       1142          1          1 
2010-02-14 2010-03-20 2010-03-21 2010-04-11 2010-05-13 2010-06-12 2010-06-29 
         1       1208        819          1          1          1          1 
2010-07-15 2010-08-17 2010-08-18 2010-08-19 2010-08-20 2010-08-21 2010-08-22 
         1        118       1440       1440       1440       1440       1348 
2010-09-25 2010-09-26 2010-09-27 2010-09-28 2010-10-24 
      1144       1440       1440       1213          1 

We see that there are consecutive days where we have null values for all the minutes in that days. We can then suppose that during that time the family was away and turned off the electricity. We can then substitute these NA with 0, cause it represent the real “consumption of energy” during that days. There are then days where there are very few null values, we can suppose that this is due to temporary malfunction of the machine. We can then substitute these NA with the previous non-null value.

# I create a new dataset where I am going to apply the substitution. 
mydata = data

#create a list with the dates where I want to substitute the null values with 0. 
l = c()
for (i in 1:length(row.names(t)))
     {
       if (t[i]>=178)
       {l = c(l, row.names(t)[i])}
}

a = which( (as.character(mydata$Date) %in% l) & is.na(mydata$Global_active_power) )
mydata[a,3:9] = c(0,0,0,0,0,0,0)

# For the remaining days we substitute the null values with the previous no-null value

for (i in 3:9)
{
mydata[,i] = na_locf(mydata[,i], option='locf')
}

Feature engineering

We create a new column with the energy that is used in the house but not in the devices connected to the submetering 1,2 and 3. In this way we have divided the total electricity used by the familiy in four different sources.

 #create a column with energy not used in submetering 1,2,3
mydata$other = as.numeric(mydata$Global_active_power-mydata$Sub_metering_1-mydata$Sub_metering_2-mydata$Sub_metering_3)

Descriptive Analysis

We made different plots to understand which useful information can be extracted from the device using different aggregation of time. Here in the notebook you can see some examples.

Consumption per minute

We look at the energy consuption per minute in the different submetering. we would like to show how it is possible to identify some electrical appliances. Indeed, every appliance has a specific pattern in the consumption of energy. This information can be used to check if there are malfunctions or if the device is “too old” and it consuming more energy then expected. For example, in the following graph you can see the consumption of energy of the dishwasher. Thanks to this level of details we have in the data, we are able to calculate the energy used for washing the dishes.

dayconsidered = subset( mydata, day(Date)==8 & month(Date)==10 & year(Date) == 2010)
g =ggplot(dayconsidered[c(1200:1440),],aes(x = c(1200:1440), y = Sub_metering_1))+ geom_point()+geom_line()+
  ggtitle(paste('Date:', weekdays(dayconsidered$Date[1]), dayconsidered$Date[1]), "dishwasher")+xlab('minutes')+
  ylab('Sub metering 1')+theme_minimal()
print(g)

If we look at the submetering two, we can see a pattern that keeps repearing in the consumption of energy. This is related with the fridge. Indeed, this appliance needs always energy at regular intervals. It is interesting that if a family monitor the energy real time, it could be possible to know immediately if the fridge remains open for too long. This can represent a useful application of the submetering devices.

dayconsidered = subset( mydata, day(Date)==8 & month(Date)==10 & year(Date) == 2010)
g =ggplot(dayconsidered,aes(x = c(1:1440), y = Sub_metering_2))+ geom_point()+geom_line()+
  ggtitle(paste('Date:', weekdays(dayconsidered$Date[1]), dayconsidered$Date[1]), "fridge")+xlab('minutes')+
  ylab('Sub metering 2')+theme_minimal()
print(g)

If we look at the energy used that it is not tracked by the submetering devices, we can see that it more difficult to capture the behaviour of the single appliances. Nevertheless, it is possible to check when the peaks of energy occur during the days and this can be a useful information to monitor that noting unexpected has happened.

dayconsidered = subset( mydata, day(Date)==8 & month(Date)==10 & year(Date) == 2010)
g =ggplot(dayconsidered,aes(x = c(1:1440), y = other))+ geom_point()+geom_line()+
  ggtitle(paste('Date:', weekdays(dayconsidered$Date[1]), dayconsidered$Date[1]))+xlab('minutes')+
  ylab('Other consumption')+theme_minimal()
print(g)

Consumption per hour

The consumption of energy aggregated by hour it is an indication of when the family is more “active” at home. This information can be used by the family to distribute better the consumption of energy in order to save money. Indeed, the electricity can have a different price depending on which hour is used.

# First we aggregate the energy by hour.
mydatah= mydata[,2:ncol(mydata)]
mydatah$hour = format( mydata$DateTime, format="%H") 
mydatahours = data.frame(mydatah %>% group_by(Date, hour ) %>% summarise_all(sum))

# We plot the energy consumption in one specific day
dayconsidered = subset(mydatahours, day(Date)== 5& month(Date)==3 & year(Date) == 2010)
  g =ggplot(dayconsidered,aes(x = c(1:24), y = Global_active_power))+ geom_point()+geom_line()+
    ggtitle(paste('Date:', weekdays(dayconsidered$Date[1]), dayconsidered$Date[1]))+xlab("hour")+theme_minimal()
  print(g)

Consumption per month

We aggregate the energy used in a mounth and we look at how the energy consumption change during the months.

# We aggregate the data 

mydatamonth = mydata
mydatamonth$monthyear = format(mydata$Date, "%Y-%m")
mydatamonth$DateTime = NULL
mydatamonth$Date = NULL
mydatamonths = mydatamonth %>%  group_by(monthyear) %>%  summarize_all(sum)
mydatamonths$year = as.factor(substring(mydatamonths$monthyear,1,4))
mydatamonths$month = as.factor(substring(mydatamonths$monthyear,6,8))

First we look at the global energy used in the different months. It is immediately clear that the energy consumption change with the seasons. Indeed we reach the peak during the winter and the trough during summer.

ggplot(data=mydatamonths[c(2:48),], aes(x=month, y= Global_active_power, group= year)) +
  geom_line(aes(color= year))+
  geom_point()+
  theme(legend.position="top")+labs(y= "Global active energy", x = "Month", color=' Years')+theme_minimal()

We can then look how much of the energy is used during the year for the different group of appliance that are connected to the different submetering devices.

# First we group the enrgy by year
mydatayears = data.frame(mydatamonths[,2: (ncol(mydatamonths)-1)] %>% group_by(year) %>% summarize_all(sum))

# Then we can plot the pie chart for one year.
ds <- data.frame(labels = c("Sub-metering 1", "Sub-metering 2", "Submetering 3", 'Other energy used'), values = unlist(mydatayears[4,c(6,7,8,9)]))
plot_ly(ds, labels = ~labels, values = ~values) %>%add_pie() %>% layout(title = "2009")

Finally, we can see how during the year the consummption of electricity changes in the different submetering.

plot_ly(mydatamonths %>% filter(year==2009), x = ~month, y = ~Sub_metering_1, type = 'bar', name = 'Sub metering 1') %>%
  add_trace(y = ~Sub_metering_2, name = 'Sub metering 2') %>%
  add_trace(y = ~Sub_metering_3, name = 'Sub metering 3') %>%
  add_trace(y =~other, name = 'Other energy used') %>%
  layout(yaxis = list(title = 'Energy'),xaxis = list(title = 'Month'), title = "2009" , barmode = 'stack')

Forecasting

Null values

When we have to forecast, we use a different approach with the null values. Indeed, since the holidays of the family were not always in the same period, we decide to fill that missing values using the usual consumption of energy in a similar period (instead of substituting them with 0). For shorter period of missing values, we consider the previous non null value.

energy = data
energy$Voltage <-NULL
energy$Global_intensity <-NULL

# As before we look at number of null values per day
DATA=data[which(is.na(data$Global_reactive_power) == TRUE),]
t = table(DATA$Date)

# In the day where we have few missing values we use the previous non null value
mv= c()
for (i in 1:length(t))
{
  if (t[i] < 178 )
  {mv = c(mv, row.names(t)[i])}
}
b = which( (as.character(energy$Date) %in% mv) )

for(i in 3:7)
{
  energy[b,i] = na_locf(energy[b,i], option='locf')
}

# For the rest of the missing values, we consider all the values of energy, that were registered in the different years in the same season, weekday and minute, and we subtitute a random value from them. 

energy$season = 'winter'
energy$season[month(energy$DateTime)>2 & month(energy$DateTime) <6 ]='spring'
energy$season[month(energy$DateTime)>5 & month(energy$DateTime) <9 ]='summer'
energy$season[month(energy$DateTime)>8 & month(energy$DateTime) <12 ]='autumn'
energy$season = as.character(energy$season)
energy$weekdays = weekdays(energy$DateTime)
ll= unique(energy$season)
l = unique(energy$weekdays)

for (i in ll){
  for (j in 0:23){
    for (k in 1:length(l))
    {
      index = which( hour(energy$DateTime) == j & as.character(energy$weekdays)== l[k] & as.character(energy$season)==i  )
      for (m in 3:7)
      {energy[index,m] = as.numeric(impute(energy[index,m],'random'))}
}}}

Forcast by month

After have examinated the data with different granularities (days, weekdays,etc.), we have decided to concentrate in predicting the energy by month. Indeed there is clearly a seasonality in this case. In the following code we show the forecast we have obtained for the global energy using two well known methods for time series forecasting.

Preparing the timeserie

To work with time series forecasting, first we have to create a timeseries object with the right frequency. Then we need to take care of the last observation (November 2010) since we are missing 4 days of that month in the data. We consider then the average daily consumption during that month and we add the “missing energy” to the last value of the time serie.

# Create a time serie object

energymonths = data.frame(energy %>% mutate(monthyear = format(mydata$Date, "%Y-%m")) %>% select(monthyear, Global_active_power) %>% group_by(monthyear) %>% summarize_all(sum))
energymonths = ts(energymonths[c(2:48),2], start=2007, frequency = 12)

# Adding the energy of 4 missing days of November 2010

December2010 = energy %>% filter(year(Date)== 2010, month(Date) == 11) %>% select(Date, Global_active_power) %>% group_by(Date) %>%  summarize_all(sum)
meanconsumption = mean(December2010$Global_active_power)
energymonths[47] = energymonths[47] + 4*meanconsumption

# Create the training and the test
training = subset(energymonths, start = 1 ,end = 36)
testing= subset(energymonths, start= 37, end= 47)

In the following graph, you can see the time serie we are analysing.

autoplot(energymonths)+theme_minimal()

We can see that we have two outliers in the time series, respectively December 2007 and August 2008. We decide to substitute the value of August with the mean between the previous and following August. We will leave December 2007 as it is, since we have just three observations in the same month (for August 2008 we have four).

# We check the outliers also with an automatic function
ouliers<- tsoutliers(energymonths) 

# We substitute the August 2008
energymonths[20] = 0.5*(energymonths[8]+energymonths[32])

We recheck the time series obtained.

autoplot(energymonths)+theme_minimal()

We create the traning and the test. For the training we will consider the first three years of data, for the test the last eleven months of the last year.

# Create the training and the test
training = subset(energymonths, start = 1 ,end = 36)
testing= subset(energymonths, start= 37, end= 47)

Looking at the time serie, we know that the prediction will be based on the seasonal component.

Exponential Smoothing models

First we look at an exponential smoothing models (Holt-Winters models). In the following image we can see the results we obtain in the training using the automatic search. In particular the values for the parameters alpha and gamma, and the distribution of the residuals.

# We look at the results obtained by the default method
fit_ets= ets(training)
summary(fit_ets)
ETS(M,N,M) 

Call:
 ets(y = training) 

  Smoothing parameters:
    alpha = 1e-04 
    gamma = 1e-04 

  Initial states:
    l = 812173.0479 
    s = 1.3161 1.2009 1.0315 0.8784 0.657 0.6484
           0.8051 0.9385 0.9447 1.1591 1.0936 1.3267

  sigma:  0.0839

     AIC     AICc      BIC 
940.7328 964.7328 964.4856 

Training set error measures:
                    ME     RMSE      MAE      MPE     MAPE      MASE
Training set -7828.695 52846.78 41746.67 -1.49427 5.381002 0.6478047
                  ACF1
Training set 0.1486854

As expected, the model suggested doesn’t have a trend component but just a seasonal one which is multiplicative. The small value we obtain for the seasonality means that we give importance to all the previous observation during the same month. We do the following test to look at the residuals.

checkresiduals(fit_ets)

    Ljung-Box test

data:  Residuals from ETS(M,N,M)
Q* = 24.53, df = 3, p-value = 1.936e-05

Model df: 14.   Total lags used: 17
There were 50 or more warnings (use warnings() to see the first 50)

The p-value in the Ljung-Box test suggest that the residuals are not uncorrelate. This means that there could be some information in the time series that we are not capturing. Finally, We look at the error on the test.

forecast1 = forecast(fit_ets,h = 11)
a1= accuracy(as.numeric(testing), forecast1$mean)
print(a2)
                ME     RMSE      MAE       MPE     MAPE       ACF1 Theil's U
Test set -8575.367 62429.64 53915.02 -1.992342 7.782517 0.08650557 0.4208285

We plot the forecasting in the test versus the actual values.

autoplot( energymonths)+autolayer(forecast1, PI = F)+theme_minimal()

We try to use also the additive method for the seasonality, so the model (M,N,A).

fit_ets_additive = ets(training, model = "MNA")
summary(fit_ets_additive)
ETS(M,N,A) 

Call:
 ets(y = training, model = "MNA") 

  Smoothing parameters:
    alpha = 0.1644 
    gamma = 1e-04 

  Initial states:
    l = 813834.1728 
    s = 266616.5 152973.6 25876.89 -101962.1 -261276.6 -270222.2
           -146805.7 -52672.78 2385.092 109901.9 30975.14 244210.2

  sigma:  0.089

     AIC     AICc      BIC 
944.9158 968.9158 968.6686 

Training set error measures:
                    ME     RMSE      MAE       MPE     MAPE     MASE
Training set -5480.248 58414.76 39359.19 -1.464873 5.198394 0.610757
                  ACF1
Training set 0.1194973
checkresiduals(fit_ets_additive)

    Ljung-Box test

data:  Residuals from ETS(M,N,A)
Q* = 19.183, df = 3, p-value = 0.0002506

Model df: 14.   Total lags used: 17

forecast2 = forecast(fit_ets_additive,h = 11)
a2= accuracy(as.numeric(testing), forecast2$mean)
print(a2)
                ME     RMSE      MAE       MPE     MAPE       ACF1 Theil's U
Test set -15883.18 63236.82 58016.64 -2.172927 7.873018 -0.1587469 0.5125032

We obtain worst results in the test respect to the previous model, as one can see looking at the RMSE, MAE and MAPE.

ARIMA

We do a similar study with the ARIMA model. We first look at the parameters given by the automatic function.

fit.arima = auto.arima(training)
summary(fit.arima)
Series: training 
ARIMA(0,0,0)(0,1,0)[12] 

sigma^2 estimated as 7.713e+09:  log likelihood=-307.25
AIC=616.5   AICc=616.68   BIC=617.68

Training set error measures:
                    ME     RMSE     MAE       MPE     MAPE      MASE
Training set -9846.649 71709.21 43234.1 -1.468423 5.774239 0.6708859
                  ACF1
Training set 0.1347819

The arima model found is the one that consider just to differenciate by year (lag 12). It means that the forecast will be the naive one, considering the observation of the same month in the previous year.

We study the residuals.

checkresiduals(fit.arima)

    Ljung-Box test

data:  Residuals from ARIMA(0,0,0)(0,1,0)[12]
Q* = 2.8616, df = 7, p-value = 0.8975

Model df: 0.   Total lags used: 7

This time the test suggests that the residuals are not correlated. We look at the error on the test.

forecast3 = forecast(fit.arima,h = 11)
a3= accuracy(as.numeric(testing), forecast2$mean)
print(a3)
                ME     RMSE      MAE       MPE     MAPE       ACF1 Theil's U
Test set -15883.18 63236.82 58016.64 -2.172927 7.873018 -0.1587469 0.5125032

We plot the forecast in the testing, that it is simply the previous observation taken during the same month.

autoplot( energymonths)+autolayer(forecast2, PI = F)+theme_minimal()

Conclusion

Comparing the results we have obtained on the test, we have seen that the best model is ETS(M,N,M). We can then forecast using this model the energy consumption of the family for 2011.

model = ets(energymonths, model = "MNM")
forecast4 = forecast(model, h = 12)
autoplot( energymonths)+autolayer(forecast4, PI = T)+theme_minimal()

LS0tDQp0aXRsZTogIkFuYWx5c2lzIG9mIEVuZXJneSBDb25zdW1wdGlvbiINCmF1dGhvcjogIkVsZW5hIEJvbmFuIg0Kb3V0cHV0OiBodG1sX25vdGVib29rDQotLS0NCg0KSW4gdGhpcyBub3RlYm9vayB5b3UgY2FuIGZpbmQgc29tZSBkZXNjcmlwdGl2ZSBhbmFseXNpcyBvZiB0aGUgZW5lcmd5IGNvbnN1bXB0aW9uIG9mIHRoZSBmYW1pbHkgYW5kIHRoZSBmb3JlY2FzdGluZyBvZiBjb25zdW1wdGlvbiBmb3IgdGhlIGZ1dHVyZSBtb250aHMuIEluZGVlZCwgdGhlIGdvYWwgb2YgdGhlIHByb2plY3QgaXMgdG8gInNlbGwiIHRoZSBzdWJtZXRlcmluZyBkZXZpY2VzIHNob3dpbmcgdG8gdGhlIGN1c3RvbWVycyBob3cgdGhleSBjYW4gYmVuZWZpdCBmcm9tIHRoZSBkYXRhIGNvbGxlY3RlZCBieSB0aGUgZGV2aWNlcy4gU28sIG9uIHRoZSBvbmUgaGFuZCwgd2Ugc2hvdyB3aGljaCB1c2VmdWwgaW5mb3JtYXRpb24gdGhleSBjYW4gb2J0YWluIGNvbnNpZGVyaW5nICJyZWFsIHRpbWUgZGF0YSIgYW5kIGFncmVnYXRlZCBkYXRhIGNvbWluZyBmcm9tIHRoZSBkZXZpY2VzLCBvbiB0aGUgb3RoZXIgaGFuZCBob3cgd2UgY2FuIGZvcmVjYXN0IHRoZSBlbmVyZ3kgY29uc3VtcHRpb24gdXNpbmcgcGFzdCBkYXRhLg0KDQojIEV4cGxvcmF0aW9uIG9mIHRoZSBEYXRhc2V0DQojIyBSIE1hcmtkb3duIHNldHRpbmdzIGFuZCBSIHBhY2thZ2VzDQoNCg0KYGBge3IgfQ0KIyBXZSBzZXQgdGhlIG9wdGlvbnMgZm9yIHRoZSBSbWFya2Rvd24NCmtuaXRyOjpvcHRzX2NodW5rJHNldCggd2FybmluZyA9IEZBTFNFKQ0KDQojIFdlIGxvYWQgdGhlIGxpYnJhcmllcyANCmlmKHJlcXVpcmUoInBhY21hbiIpPT0iRkFMU0UiKXsNCiAgaW5zdGFsbC5wYWNrYWdlcygicGFjbWFuIikNCn0NCnBhY21hbjo6cF9sb2FkKHBsb3RseSwgY29ycnBsb3QsIGx1YnJpZGF0ZSwgaG1zLCBIbWlzYywgaW1wdXRlVFMscnN0dWRpb2FwaSxra25uLGdncGxvdDIsIHJhbmRvbUZvcmVzdCwgZHBseXIsIHNoaW55LCB0aWR5ciwgZ2dwbG90MiwgY2FyZXQsIGZvcmVjYXN0KQ0KDQpgYGANCg0KIyMgRGF0YXNldCBzdHJ1Y3R1cmUNCldlIGxvb2sgYXQgdGhlIGRhdGFzZXQgc3RydWN0dXJlLiANCg0KYGBge3J9DQpkYXRhIDwtIHJlYWQuZGVsaW0oImhvdXNlaG9sZF9wb3dlcl9jb25zdW1wdGlvbi50eHQiLGhlYWRlciA9IFRSVUUsIHNlcD0iOyIpDQpoZWFkKGRhdGEpDQpgYGANCg0KIyMgRGF0YSBDbGVhbmluaW5nDQoNCldlIHB1dCB0aGUgdmFyaWFibGVzIGluIHRoZSByaWdodCB0eXBlLiANCg0KYGBge3IgfQ0KZGF0YSRHbG9iYWxfcmVhY3RpdmVfcG93ZXIgPSBzdXBwcmVzc1dhcm5pbmdzKGFzLm51bWVyaWMoYXMuY2hhcmFjdGVyKGRhdGEkR2xvYmFsX3JlYWN0aXZlX3Bvd2VyKSkpDQpkYXRhJEdsb2JhbF9hY3RpdmVfcG93ZXIgPSBzdXBwcmVzc1dhcm5pbmdzKGFzLm51bWVyaWMoYXMuY2hhcmFjdGVyKGRhdGEkR2xvYmFsX2FjdGl2ZV9wb3dlcikpKQ0KZGF0YSRTdWJfbWV0ZXJpbmdfMSA9IHN1cHByZXNzV2FybmluZ3MoYXMubnVtZXJpYyhhcy5jaGFyYWN0ZXIoZGF0YSRTdWJfbWV0ZXJpbmdfMSkpKQ0KZGF0YSRTdWJfbWV0ZXJpbmdfMj0gc3VwcHJlc3NXYXJuaW5ncyhhcy5udW1lcmljKGFzLmNoYXJhY3RlcihkYXRhJFN1Yl9tZXRlcmluZ18yKSkpDQpkYXRhJEdsb2JhbF9pbnRlbnNpdHkgPSBzdXBwcmVzc1dhcm5pbmdzKGFzLm51bWVyaWMoYXMuY2hhcmFjdGVyKGRhdGEkR2xvYmFsX2ludGVuc2l0eSkpKQ0KZGF0YSRWb2x0YWdlID0gc3VwcHJlc3NXYXJuaW5ncyhhcy5udW1lcmljKGFzLmNoYXJhY3RlcihkYXRhJFZvbHRhZ2UpKSkNCmBgYCANCg0KV2UgY2hhbmdlIHRoZSB1bml0IG9mIG1lYXN1cmUgb2YgdGhlIGFjdGl2ZSBwb3dlciBhbmQgcmVhY3RpdmUgcG93ZXIgaW4gb3JkZXIgdG8gYmUgdGhlIHNhbWUgb2YgdGhlIHN1Ym1ldGVyaW5nICh3YXR0LWhvdXIpLg0KV2UgY3JlYXRlIGEgY29sdW1uIHdpdGggdGhlIGRhdGUgYW5kIHRoZSB0aW1lIGpvaW5lZC4gRmluYWxseSB3ZSBjaGFuZ2UgdGhlIHRpbWUgaW4gb3JkZXIgdG8gdGFrZSBpbiBjb25zaWRlcmF0aW9uIHRoZSBzb2xhciBhbmQgbGVnYWwgaG91ci4gDQoNCmBgYHtyfQ0KI0NoYW5nZSB0aGUgdW5pdGUgbWVhc3VyZQ0KZGF0YSRHbG9iYWxfYWN0aXZlX3Bvd2VyID0gZGF0YSRHbG9iYWxfYWN0aXZlX3Bvd2VyICogMTAwMCAvIDYwDQpkYXRhJEdsb2JhbF9yZWFjdGl2ZV9wb3dlciA9IGRhdGEkR2xvYmFsX3JlYWN0aXZlX3Bvd2VyICogMTAwMCAvIDYwDQoNCiNjcmVhdGUgYSBjb2x1bW4gaW4gdGhlIGZvcm1hdCBzdHJwdGltZQ0KDQpkYXRhPC1jYmluZChkYXRhLHBhc3RlKGRhdGEkRGF0ZSxkYXRhJFRpbWUpLCBzdHJpbmdzQXNGYWN0b3JzPUZBTFNFKQ0KY29sbmFtZXMoZGF0YSlbMTBdIDwtIkRhdGVUaW1lIg0KZGF0YTwtIGRhdGFbLGMobmNvbChkYXRhKSwgMToobmNvbChkYXRhKS0xKSldDQpkYXRhJERhdGVUaW1lIDwtIHN0cnB0aW1lKGRhdGEkRGF0ZVRpbWUsICIlZC8lbS8lWSAlSDolTTolUyIsIHR6PSJHTVQiKQ0KZGF0YSREYXRlIDwtIE5VTEwNCmRhdGEkVGltZSA8LU5VTEwNCiNzb2xhciBhbmQgbGVnYWwgaG91cg0KDQphID0gd2hpY2goZGF0YSREYXRlVGltZSA9PSBhcy5QT1NJWGN0KCIyMDA3LTAzLTI1IDAyOjAwOjAwIiwgdHogPSAiR01UIikgfCBkYXRhJERhdGVUaW1lID09IGFzLlBPU0lYY3QoICIyMDA3LTEwLTI4IDAxOjU5OjAwIiwgdHogPSAiR01UIikpIA0KZGF0YVthWzFdOmFbMl0sMV0gPSBkYXRhW2FbMV06YVsyXSwxXSszNjAwDQoNCmEgPSB3aGljaChkYXRhJERhdGVUaW1lID09IGFzLlBPU0lYY3QoIjIwMDgtMDMtMzAgMDI6MDA6MDAiLCB0eiA9ICJHTVQiKSB8IGRhdGEkRGF0ZVRpbWUgPT1hcy5QT1NJWGN0KCAiMjAwOC0xMC0yNiAwMTo1OTowMCIsIHR6ID0gIkdNVCIpKSANCmRhdGFbYVsxXTphWzJdLDFdID0gZGF0YVthWzFdOmFbMl0sMV0rMzYwMA0KDQphID0gd2hpY2goZGF0YSREYXRlVGltZSA9PSBhcy5QT1NJWGN0KCIyMDA5LTAzLTI5IDAyOjAwOjAwIiwgdHogPSAiR01UIikgfCBkYXRhJERhdGVUaW1lID09YXMuUE9TSVhjdCggIjIwMDktMTAtMjUgMDE6NTk6MDAiLCB0eiA9ICJHTVQiKSkgDQpkYXRhW2FbMV06YVsyXSwxXSA9IGRhdGFbYVsxXTphWzJdLDFdKzM2MDANCg0KYSA9IHdoaWNoKGRhdGEkRGF0ZVRpbWUgPT0gYXMuUE9TSVhjdCgiMjAxMC0wMy0yOCAwMjowMDowMCIsIHR6ID0gIkdNVCIpIHwgZGF0YSREYXRlVGltZSA9PWFzLlBPU0lYY3QoICIyMDEwLTEwLTMxIDAxOjU5OjAwIiwgdHogPSAiR01UIikpIA0KZGF0YVthWzFdOmFbMl0sMV0gPSBkYXRhW2FbMV06YVsyXSwxXSszNjAwDQoNCiMgUmVjcmVhdGUgdGhlIGRhdGUgY29sdW1uIGFuZCByZW9yZGVyIHRoZSBvcmRlciBvZiB0aGUgY29sdW1ucy4gDQpkYXRhJERhdGUgPC0gYXMuRGF0ZShkYXRhJERhdGVUaW1lKQ0KZGF0YT1kYXRhWyxjKDEsOSwyOjgpXQ0KYGBgDQoNCldlIGxvb2sgYXQgc29tZSBzdW1tYXJ5IG1lYXN1cmVzIG9mIHRoZSB2YXJpYWJsZXMuDQpgYGB7ciByZXN1bHRzID0gJ2hvbGQnfQ0Kc3VtbWFyeShkYXRhKQ0KYGBgDQpUaGUgbWlzc2luZyB2YWx1ZXMgYXJlIHRoZSBzYW1lIHF1YW50aXR5IGZvciBhbGwgdGhlIG51bWVyaWNhbCB2YXJpYWJsZXMuIE1vcmV2b3JlIHdlIGhhY2UgY2hlY2tlZCB0aGV5IGFyZSBtaXNzaW5nIGluIHRoZSBzYW1lIHJvd3MuIA0KV2UgdGFrZSBhIGxvb2sgYXQgaG93IHRoZXkgYXJlIGRpc3RyaWJ1dGVkLiBJbiBwYXJ0aWN1YXIgYXQgdGhlaXIgZGFpbHkgcXVhbnRpdHkuIA0KIA0KYGBge3IgcmVzdWx0cyA9ICdob2xkJ30NCiNjcmVhdGUgYSB0YWJsZSB3aXRoIHRoZSBtaXNzaW5nIHZhbHVlcyANCkRBVEE9ZGF0YVt3aGljaChpcy5uYShkYXRhJEdsb2JhbF9yZWFjdGl2ZV9wb3dlcikgPT0gVFJVRSksXQ0KdCA9IHRhYmxlKERBVEEkRGF0ZSkNCnByaW50KHQpDQpgYGANCg0KV2Ugc2VlIHRoYXQgdGhlcmUgYXJlIGNvbnNlY3V0aXZlIGRheXMgd2hlcmUgd2UgaGF2ZSBudWxsIHZhbHVlcyBmb3IgYWxsIHRoZSBtaW51dGVzIGluIHRoYXQgZGF5cy4gV2UgY2FuIHRoZW4gc3VwcG9zZSB0aGF0IGR1cmluZyB0aGF0IHRpbWUgdGhlIGZhbWlseSB3YXMgYXdheSBhbmQgdHVybmVkIG9mZiB0aGUgZWxlY3RyaWNpdHkuIFdlIGNhbiB0aGVuIHN1YnN0aXR1dGUgdGhlc2UgTkEgd2l0aCAwLCBjYXVzZSBpdCByZXByZXNlbnQgdGhlIHJlYWwgImNvbnN1bXB0aW9uIG9mIGVuZXJneSIgZHVyaW5nIHRoYXQgZGF5cy4gVGhlcmUgYXJlIHRoZW4gZGF5cyB3aGVyZSB0aGVyZSBhcmUgdmVyeSBmZXcgbnVsbCB2YWx1ZXMsIHdlIGNhbiBzdXBwb3NlIHRoYXQgdGhpcyBpcyBkdWUgdG8gdGVtcG9yYXJ5IG1hbGZ1bmN0aW9uIG9mIHRoZSBtYWNoaW5lLiBXZSBjYW4gdGhlbiBzdWJzdGl0dXRlIHRoZXNlIE5BIHdpdGggdGhlIHByZXZpb3VzIG5vbi1udWxsIHZhbHVlLiANCg0KDQpgYGB7cn0NCiMgSSBjcmVhdGUgYSBuZXcgZGF0YXNldCB3aGVyZSBJIGFtIGdvaW5nIHRvIGFwcGx5IHRoZSBzdWJzdGl0dXRpb24uIA0KbXlkYXRhID0gZGF0YQ0KDQojY3JlYXRlIGEgbGlzdCB3aXRoIHRoZSBkYXRlcyB3aGVyZSBJIHdhbnQgdG8gc3Vic3RpdHV0ZSB0aGUgbnVsbCB2YWx1ZXMgd2l0aCAwLiANCmwgPSBjKCkNCmZvciAoaSBpbiAxOmxlbmd0aChyb3cubmFtZXModCkpKQ0KICAgICB7DQogICAgICAgaWYgKHRbaV0+PTE3OCkNCiAgICAgICB7bCA9IGMobCwgcm93Lm5hbWVzKHQpW2ldKX0NCn0NCg0KYSA9IHdoaWNoKCAoYXMuY2hhcmFjdGVyKG15ZGF0YSREYXRlKSAlaW4lIGwpICYgaXMubmEobXlkYXRhJEdsb2JhbF9hY3RpdmVfcG93ZXIpICkNCm15ZGF0YVthLDM6OV0gPSBjKDAsMCwwLDAsMCwwLDApDQoNCiMgRm9yIHRoZSByZW1haW5pbmcgZGF5cyB3ZSBzdWJzdGl0dXRlIHRoZSBudWxsIHZhbHVlcyB3aXRoIHRoZSBwcmV2aW91cyBuby1udWxsIHZhbHVlDQoNCmZvciAoaSBpbiAzOjkpDQp7DQpteWRhdGFbLGldID0gbmFfbG9jZihteWRhdGFbLGldLCBvcHRpb249J2xvY2YnKQ0KfQ0KDQpgYGANCg0KDQoNCiMjIEZlYXR1cmUgZW5naW5lZXJpbmcgDQoNCldlIGNyZWF0ZSBhIG5ldyBjb2x1bW4gd2l0aCB0aGUgZW5lcmd5IHRoYXQgaXMgdXNlZCBpbiB0aGUgaG91c2UgYnV0IG5vdCBpbiB0aGUgZGV2aWNlcyBjb25uZWN0ZWQgdG8gdGhlIHN1Ym1ldGVyaW5nIDEsMiBhbmQgMy4gSW4gdGhpcyB3YXkgd2UgaGF2ZSBkaXZpZGVkIHRoZSB0b3RhbCBlbGVjdHJpY2l0eSB1c2VkIGJ5IHRoZSBmYW1pbGl5IGluIGZvdXIgZGlmZmVyZW50IHNvdXJjZXMuDQoNCmBgYHtyfQ0KICNjcmVhdGUgYSBjb2x1bW4gd2l0aCBlbmVyZ3kgbm90IHVzZWQgaW4gc3VibWV0ZXJpbmcgMSwyLDMNCm15ZGF0YSRvdGhlciA9IGFzLm51bWVyaWMobXlkYXRhJEdsb2JhbF9hY3RpdmVfcG93ZXItbXlkYXRhJFN1Yl9tZXRlcmluZ18xLW15ZGF0YSRTdWJfbWV0ZXJpbmdfMi1teWRhdGEkU3ViX21ldGVyaW5nXzMpDQoNCmBgYA0KDQojIERlc2NyaXB0aXZlIEFuYWx5c2lzIA0KV2UgbWFkZSBkaWZmZXJlbnQgcGxvdHMgdG8gdW5kZXJzdGFuZCB3aGljaCB1c2VmdWwgaW5mb3JtYXRpb24gY2FuIGJlIGV4dHJhY3RlZCBmcm9tIHRoZSBkZXZpY2UgdXNpbmcgZGlmZmVyZW50IGFnZ3JlZ2F0aW9uIG9mIHRpbWUuIEhlcmUgaW4gdGhlIG5vdGVib29rIHlvdSBjYW4gc2VlIHNvbWUgZXhhbXBsZXMuDQoNCiMjIENvbnN1bXB0aW9uIHBlciBtaW51dGUNCg0KV2UgbG9vayBhdCB0aGUgZW5lcmd5IGNvbnN1cHRpb24gcGVyIG1pbnV0ZSBpbiB0aGUgZGlmZmVyZW50IHN1Ym1ldGVyaW5nLiB3ZSB3b3VsZCBsaWtlIHRvIHNob3cgaG93IGl0IGlzIHBvc3NpYmxlIHRvIGlkZW50aWZ5IHNvbWUgZWxlY3RyaWNhbCBhcHBsaWFuY2VzLiBJbmRlZWQsIGV2ZXJ5IGFwcGxpYW5jZSBoYXMgYSBzcGVjaWZpYyBwYXR0ZXJuIGluIHRoZSBjb25zdW1wdGlvbiBvZiBlbmVyZ3kuIFRoaXMgaW5mb3JtYXRpb24gY2FuIGJlIHVzZWQgdG8gY2hlY2sgaWYgdGhlcmUgYXJlIG1hbGZ1bmN0aW9ucyBvciBpZiB0aGUgZGV2aWNlIGlzICJ0b28gb2xkIiBhbmQgaXQgY29uc3VtaW5nIG1vcmUgZW5lcmd5IHRoZW4gZXhwZWN0ZWQuIEZvciBleGFtcGxlLCBpbiB0aGUgZm9sbG93aW5nIGdyYXBoIHlvdSBjYW4gc2VlIHRoZSBjb25zdW1wdGlvbiBvZiBlbmVyZ3kgb2YgdGhlIGRpc2h3YXNoZXIuIFRoYW5rcyB0byB0aGlzIGxldmVsIG9mIGRldGFpbHMgd2UgaGF2ZSBpbiB0aGUgZGF0YSwgd2UgYXJlIGFibGUgdG8gY2FsY3VsYXRlIHRoZSBlbmVyZ3kgdXNlZCBmb3Igd2FzaGluZyB0aGUgZGlzaGVzLg0KDQpgYGB7cn0NCmRheWNvbnNpZGVyZWQgPSBzdWJzZXQoIG15ZGF0YSwgZGF5KERhdGUpPT04ICYgbW9udGgoRGF0ZSk9PTEwICYgeWVhcihEYXRlKSA9PSAyMDEwKQ0KZyA9Z2dwbG90KGRheWNvbnNpZGVyZWRbYygxMjAwOjE0NDApLF0sYWVzKHggPSBjKDEyMDA6MTQ0MCksIHkgPSBTdWJfbWV0ZXJpbmdfMSkpKyBnZW9tX3BvaW50KCkrZ2VvbV9saW5lKCkrDQogIGdndGl0bGUocGFzdGUoJ0RhdGU6Jywgd2Vla2RheXMoZGF5Y29uc2lkZXJlZCREYXRlWzFdKSwgZGF5Y29uc2lkZXJlZCREYXRlWzFdKSwgImRpc2h3YXNoZXIiKSt4bGFiKCdtaW51dGVzJykrDQogIHlsYWIoJ1N1YiBtZXRlcmluZyAxJykrdGhlbWVfbWluaW1hbCgpDQpwcmludChnKQ0KYGBgDQoNCklmIHdlIGxvb2sgYXQgdGhlIHN1Ym1ldGVyaW5nIHR3bywgd2UgY2FuIHNlZSBhIHBhdHRlcm4gdGhhdCBrZWVwcyByZXBlYXJpbmcgaW4gdGhlIGNvbnN1bXB0aW9uIG9mIGVuZXJneS4gVGhpcyBpcyByZWxhdGVkIHdpdGggdGhlIGZyaWRnZS4gSW5kZWVkLCB0aGlzIGFwcGxpYW5jZSBuZWVkcyBhbHdheXMgZW5lcmd5IGF0IHJlZ3VsYXIgaW50ZXJ2YWxzLiBJdCBpcyBpbnRlcmVzdGluZyB0aGF0IGlmIGEgZmFtaWx5IG1vbml0b3IgdGhlIGVuZXJneSByZWFsIHRpbWUsIGl0IGNvdWxkIGJlIHBvc3NpYmxlIHRvIGtub3cgaW1tZWRpYXRlbHkgaWYgdGhlIGZyaWRnZSByZW1haW5zIG9wZW4gZm9yIHRvbyBsb25nLiBUaGlzIGNhbiByZXByZXNlbnQgYSB1c2VmdWwgYXBwbGljYXRpb24gb2YgdGhlIHN1Ym1ldGVyaW5nIGRldmljZXMuDQoNCmBgYHtyfQ0KZGF5Y29uc2lkZXJlZCA9IHN1YnNldCggbXlkYXRhLCBkYXkoRGF0ZSk9PTggJiBtb250aChEYXRlKT09MTAgJiB5ZWFyKERhdGUpID09IDIwMTApDQpnID1nZ3Bsb3QoZGF5Y29uc2lkZXJlZCxhZXMoeCA9IGMoMToxNDQwKSwgeSA9IFN1Yl9tZXRlcmluZ18yKSkrIGdlb21fcG9pbnQoKStnZW9tX2xpbmUoKSsNCiAgZ2d0aXRsZShwYXN0ZSgnRGF0ZTonLCB3ZWVrZGF5cyhkYXljb25zaWRlcmVkJERhdGVbMV0pLCBkYXljb25zaWRlcmVkJERhdGVbMV0pLCAiZnJpZGdlIikreGxhYignbWludXRlcycpKw0KICB5bGFiKCdTdWIgbWV0ZXJpbmcgMicpK3RoZW1lX21pbmltYWwoKQ0KcHJpbnQoZykNCmBgYA0KDQpJZiB3ZSBsb29rIGF0IHRoZSBlbmVyZ3kgdXNlZCB0aGF0IGl0IGlzIG5vdCB0cmFja2VkIGJ5IHRoZSBzdWJtZXRlcmluZyBkZXZpY2VzLCB3ZSBjYW4gc2VlIHRoYXQgaXQgbW9yZSBkaWZmaWN1bHQgdG8gY2FwdHVyZSB0aGUgYmVoYXZpb3VyIG9mIHRoZSBzaW5nbGUgYXBwbGlhbmNlcy4gTmV2ZXJ0aGVsZXNzLCBpdCBpcyBwb3NzaWJsZSB0byBjaGVjayB3aGVuIHRoZSBwZWFrcyBvZiBlbmVyZ3kgb2NjdXIgZHVyaW5nIHRoZSBkYXlzIGFuZCB0aGlzIGNhbiBiZSBhIHVzZWZ1bCBpbmZvcm1hdGlvbiB0byBtb25pdG9yIHRoYXQgbm90aW5nIHVuZXhwZWN0ZWQgaGFzIGhhcHBlbmVkLg0KDQoNCmBgYHtyfQ0KZGF5Y29uc2lkZXJlZCA9IHN1YnNldCggbXlkYXRhLCBkYXkoRGF0ZSk9PTggJiBtb250aChEYXRlKT09MTAgJiB5ZWFyKERhdGUpID09IDIwMTApDQpnID1nZ3Bsb3QoZGF5Y29uc2lkZXJlZCxhZXMoeCA9IGMoMToxNDQwKSwgeSA9IG90aGVyKSkrIGdlb21fcG9pbnQoKStnZW9tX2xpbmUoKSsNCiAgZ2d0aXRsZShwYXN0ZSgnRGF0ZTonLCB3ZWVrZGF5cyhkYXljb25zaWRlcmVkJERhdGVbMV0pLCBkYXljb25zaWRlcmVkJERhdGVbMV0pKSt4bGFiKCdtaW51dGVzJykrDQogIHlsYWIoJ090aGVyIGNvbnN1bXB0aW9uJykrdGhlbWVfbWluaW1hbCgpDQpwcmludChnKQ0KYGBgDQoNCiMjIyBDb25zdW1wdGlvbiBwZXIgaG91cg0KVGhlIGNvbnN1bXB0aW9uIG9mIGVuZXJneSBhZ2dyZWdhdGVkIGJ5IGhvdXIgaXQgaXMgYW4gaW5kaWNhdGlvbiBvZiB3aGVuIHRoZSBmYW1pbHkgaXMgbW9yZSAiYWN0aXZlIiBhdCBob21lLiBUaGlzIGluZm9ybWF0aW9uIGNhbiBiZSB1c2VkIGJ5IHRoZSBmYW1pbHkgdG8gZGlzdHJpYnV0ZSBiZXR0ZXIgdGhlIGNvbnN1bXB0aW9uIG9mIGVuZXJneSBpbiBvcmRlciB0byBzYXZlIG1vbmV5LiBJbmRlZWQsIHRoZSBlbGVjdHJpY2l0eSBjYW4gaGF2ZSBhIGRpZmZlcmVudCBwcmljZSBkZXBlbmRpbmcgb24gd2hpY2ggaG91ciBpcyB1c2VkLg0KDQpgYGB7cn0NCiMgRmlyc3Qgd2UgYWdncmVnYXRlIHRoZSBlbmVyZ3kgYnkgaG91ci4NCm15ZGF0YWg9IG15ZGF0YVssMjpuY29sKG15ZGF0YSldDQpteWRhdGFoJGhvdXIgPSBmb3JtYXQoIG15ZGF0YSREYXRlVGltZSwgZm9ybWF0PSIlSCIpIA0KbXlkYXRhaG91cnMgPSBkYXRhLmZyYW1lKG15ZGF0YWggJT4lIGdyb3VwX2J5KERhdGUsIGhvdXIgKSAlPiUgc3VtbWFyaXNlX2FsbChzdW0pKQ0KDQojIFdlIHBsb3QgdGhlIGVuZXJneSBjb25zdW1wdGlvbiBpbiBvbmUgc3BlY2lmaWMgZGF5DQpkYXljb25zaWRlcmVkID0gc3Vic2V0KG15ZGF0YWhvdXJzLCBkYXkoRGF0ZSk9PSA1JiBtb250aChEYXRlKT09MyAmIHllYXIoRGF0ZSkgPT0gMjAxMCkNCiAgZyA9Z2dwbG90KGRheWNvbnNpZGVyZWQsYWVzKHggPSBjKDE6MjQpLCB5ID0gR2xvYmFsX2FjdGl2ZV9wb3dlcikpKyBnZW9tX3BvaW50KCkrZ2VvbV9saW5lKCkrDQogICAgZ2d0aXRsZShwYXN0ZSgnRGF0ZTonLCB3ZWVrZGF5cyhkYXljb25zaWRlcmVkJERhdGVbMV0pLCBkYXljb25zaWRlcmVkJERhdGVbMV0pKSt4bGFiKCJob3VyIikrdGhlbWVfbWluaW1hbCgpDQogIHByaW50KGcpDQpgYGANCg0KIyMgQ29uc3VtcHRpb24gcGVyIG1vbnRoDQoNCldlIGFnZ3JlZ2F0ZSB0aGUgZW5lcmd5IHVzZWQgaW4gYSBtb3VudGggYW5kIHdlIGxvb2sgYXQgaG93IHRoZSBlbmVyZ3kgY29uc3VtcHRpb24gY2hhbmdlIGR1cmluZyB0aGUgbW9udGhzLiANCmBgYHtyfQ0KIyBXZSBhZ2dyZWdhdGUgdGhlIGRhdGEgDQpteWRhdGFtb250aCA9IG15ZGF0YQ0KbXlkYXRhbW9udGgkbW9udGh5ZWFyID0gZm9ybWF0KG15ZGF0YSREYXRlLCAiJVktJW0iKQ0KbXlkYXRhbW9udGgkRGF0ZVRpbWUgPSBOVUxMDQpteWRhdGFtb250aCREYXRlID0gTlVMTA0KbXlkYXRhbW9udGhzID0gbXlkYXRhbW9udGggJT4lICBncm91cF9ieShtb250aHllYXIpICU+JSAgc3VtbWFyaXplX2FsbChzdW0pDQpteWRhdGFtb250aHMkeWVhciA9IGFzLmZhY3RvcihzdWJzdHJpbmcobXlkYXRhbW9udGhzJG1vbnRoeWVhciwxLDQpKQ0KbXlkYXRhbW9udGhzJG1vbnRoID0gYXMuZmFjdG9yKHN1YnN0cmluZyhteWRhdGFtb250aHMkbW9udGh5ZWFyLDYsOCkpDQoNCmBgYA0KDQpGaXJzdCB3ZSBsb29rIGF0IHRoZSBnbG9iYWwgZW5lcmd5IHVzZWQgaW4gdGhlIGRpZmZlcmVudCBtb250aHMuIEl0IGlzIGltbWVkaWF0ZWx5IGNsZWFyIHRoYXQgdGhlIGVuZXJneSBjb25zdW1wdGlvbiBjaGFuZ2Ugd2l0aCB0aGUgc2Vhc29ucy4gSW5kZWVkIHdlIHJlYWNoIHRoZSBwZWFrIGR1cmluZyB0aGUgd2ludGVyIGFuZCB0aGUgdHJvdWdoIGR1cmluZyBzdW1tZXIuIA0KDQpgYGB7cn0NCmdncGxvdChkYXRhPW15ZGF0YW1vbnRoc1tjKDI6NDgpLF0sIGFlcyh4PW1vbnRoLCB5PSBHbG9iYWxfYWN0aXZlX3Bvd2VyLCBncm91cD0geWVhcikpICsNCiAgZ2VvbV9saW5lKGFlcyhjb2xvcj0geWVhcikpKw0KICBnZW9tX3BvaW50KCkrDQogIHRoZW1lKGxlZ2VuZC5wb3NpdGlvbj0idG9wIikrbGFicyh5PSAiR2xvYmFsIGFjdGl2ZSBlbmVyZ3kiLCB4ID0gIk1vbnRoIiwgY29sb3I9JyBZZWFycycpK3RoZW1lX21pbmltYWwoKQ0KDQpgYGANCg0KV2UgY2FuIHRoZW4gbG9vayBob3cgbXVjaCBvZiB0aGUgZW5lcmd5IGlzIHVzZWQgZHVyaW5nIHRoZSB5ZWFyIGZvciB0aGUgZGlmZmVyZW50IGdyb3VwIG9mIGFwcGxpYW5jZSB0aGF0IGFyZSBjb25uZWN0ZWQgdG8gdGhlIGRpZmZlcmVudCBzdWJtZXRlcmluZyBkZXZpY2VzLg0KDQpgYGB7cn0NCiMgRmlyc3Qgd2UgZ3JvdXAgdGhlIGVucmd5IGJ5IHllYXINCm15ZGF0YXllYXJzID0gZGF0YS5mcmFtZShteWRhdGFtb250aHNbLDI6IChuY29sKG15ZGF0YW1vbnRocyktMSldICU+JSBncm91cF9ieSh5ZWFyKSAlPiUgc3VtbWFyaXplX2FsbChzdW0pKQ0KDQojIFRoZW4gd2UgY2FuIHBsb3QgdGhlIHBpZSBjaGFydCBmb3Igb25lIHllYXIuDQpkcyA8LSBkYXRhLmZyYW1lKGxhYmVscyA9IGMoIlN1Yi1tZXRlcmluZyAxIiwgIlN1Yi1tZXRlcmluZyAyIiwgIlN1Ym1ldGVyaW5nIDMiLCAnT3RoZXIgZW5lcmd5IHVzZWQnKSwgdmFsdWVzID0gdW5saXN0KG15ZGF0YXllYXJzWzQsYyg2LDcsOCw5KV0pKQ0KcGxvdF9seShkcywgbGFiZWxzID0gfmxhYmVscywgdmFsdWVzID0gfnZhbHVlcykgJT4lYWRkX3BpZSgpICU+JSBsYXlvdXQodGl0bGUgPSAiMjAwOSIpDQpgYGANCg0KRmluYWxseSwgd2UgY2FuIHNlZSBob3cgZHVyaW5nIHRoZSB5ZWFyIHRoZSBjb25zdW1tcHRpb24gb2YgZWxlY3RyaWNpdHkgY2hhbmdlcyBpbiB0aGUgZGlmZmVyZW50IHN1Ym1ldGVyaW5nLiANCg0KYGBge3J9DQpwbG90X2x5KG15ZGF0YW1vbnRocyAlPiUgZmlsdGVyKHllYXI9PTIwMDkpLCB4ID0gfm1vbnRoLCB5ID0gflN1Yl9tZXRlcmluZ18xLCB0eXBlID0gJ2JhcicsIG5hbWUgPSAnU3ViIG1ldGVyaW5nIDEnKSAlPiUNCiAgYWRkX3RyYWNlKHkgPSB+U3ViX21ldGVyaW5nXzIsIG5hbWUgPSAnU3ViIG1ldGVyaW5nIDInKSAlPiUNCiAgYWRkX3RyYWNlKHkgPSB+U3ViX21ldGVyaW5nXzMsIG5hbWUgPSAnU3ViIG1ldGVyaW5nIDMnKSAlPiUNCiAgYWRkX3RyYWNlKHkgPX5vdGhlciwgbmFtZSA9ICdPdGhlciBlbmVyZ3kgdXNlZCcpICU+JQ0KICBsYXlvdXQoeWF4aXMgPSBsaXN0KHRpdGxlID0gJ0VuZXJneScpLHhheGlzID0gbGlzdCh0aXRsZSA9ICdNb250aCcpLCB0aXRsZSA9ICIyMDA5IiAsIGJhcm1vZGUgPSAnc3RhY2snKQ0KYGBgDQoNCiMgRm9yZWNhc3RpbmcNCg0KIyMgTnVsbCB2YWx1ZXMNCg0KV2hlbiB3ZSBoYXZlIHRvIGZvcmVjYXN0LCB3ZSB1c2UgYSBkaWZmZXJlbnQgYXBwcm9hY2ggd2l0aCB0aGUgbnVsbCB2YWx1ZXMuIEluZGVlZCwgc2luY2UgdGhlIGhvbGlkYXlzIG9mIHRoZSBmYW1pbHkgd2VyZSBub3QgYWx3YXlzIGluIHRoZSBzYW1lIHBlcmlvZCwgd2UgZGVjaWRlIHRvIGZpbGwgdGhhdCBtaXNzaW5nIHZhbHVlcyB1c2luZyB0aGUgdXN1YWwgY29uc3VtcHRpb24gb2YgZW5lcmd5IGluIGEgc2ltaWxhciBwZXJpb2QgKGluc3RlYWQgb2Ygc3Vic3RpdHV0aW5nIHRoZW0gd2l0aCAwKS4gRm9yIHNob3J0ZXIgcGVyaW9kIG9mIG1pc3NpbmcgdmFsdWVzLCB3ZSBjb25zaWRlciB0aGUgcHJldmlvdXMgbm9uIG51bGwgdmFsdWUuDQoNCmBgYHtyfQ0KZW5lcmd5ID0gZGF0YQ0KZW5lcmd5JFZvbHRhZ2UgPC1OVUxMDQplbmVyZ3kkR2xvYmFsX2ludGVuc2l0eSA8LU5VTEwNCg0KIyBBcyBiZWZvcmUgd2UgbG9vayBhdCBudW1iZXIgb2YgbnVsbCB2YWx1ZXMgcGVyIGRheQ0KREFUQT1kYXRhW3doaWNoKGlzLm5hKGRhdGEkR2xvYmFsX3JlYWN0aXZlX3Bvd2VyKSA9PSBUUlVFKSxdDQp0ID0gdGFibGUoREFUQSREYXRlKQ0KDQojIEluIHRoZSBkYXkgd2hlcmUgd2UgaGF2ZSBmZXcgbWlzc2luZyB2YWx1ZXMgd2UgdXNlIHRoZSBwcmV2aW91cyBub24gbnVsbCB2YWx1ZQ0KbXY9IGMoKQ0KZm9yIChpIGluIDE6bGVuZ3RoKHQpKQ0Kew0KICBpZiAodFtpXSA8IDE3OCApDQogIHttdiA9IGMobXYsIHJvdy5uYW1lcyh0KVtpXSl9DQp9DQpiID0gd2hpY2goIChhcy5jaGFyYWN0ZXIoZW5lcmd5JERhdGUpICVpbiUgbXYpICkNCg0KZm9yKGkgaW4gMzo3KQ0Kew0KICBlbmVyZ3lbYixpXSA9IG5hX2xvY2YoZW5lcmd5W2IsaV0sIG9wdGlvbj0nbG9jZicpDQp9DQoNCiMgRm9yIHRoZSByZXN0IG9mIHRoZSBtaXNzaW5nIHZhbHVlcywgd2UgY29uc2lkZXIgYWxsIHRoZSB2YWx1ZXMgb2YgZW5lcmd5LCB0aGF0IHdlcmUgcmVnaXN0ZXJlZCBpbiB0aGUgZGlmZmVyZW50IHllYXJzIGluIHRoZSBzYW1lIHNlYXNvbiwgd2Vla2RheSBhbmQgbWludXRlLCBhbmQgd2Ugc3VidGl0dXRlIGEgcmFuZG9tIHZhbHVlIGZyb20gdGhlbS4gDQoNCmVuZXJneSRzZWFzb24gPSAnd2ludGVyJw0KZW5lcmd5JHNlYXNvblttb250aChlbmVyZ3kkRGF0ZVRpbWUpPjIgJiBtb250aChlbmVyZ3kkRGF0ZVRpbWUpIDw2IF09J3NwcmluZycNCmVuZXJneSRzZWFzb25bbW9udGgoZW5lcmd5JERhdGVUaW1lKT41ICYgbW9udGgoZW5lcmd5JERhdGVUaW1lKSA8OSBdPSdzdW1tZXInDQplbmVyZ3kkc2Vhc29uW21vbnRoKGVuZXJneSREYXRlVGltZSk+OCAmIG1vbnRoKGVuZXJneSREYXRlVGltZSkgPDEyIF09J2F1dHVtbicNCmVuZXJneSRzZWFzb24gPSBhcy5jaGFyYWN0ZXIoZW5lcmd5JHNlYXNvbikNCmVuZXJneSR3ZWVrZGF5cyA9IHdlZWtkYXlzKGVuZXJneSREYXRlVGltZSkNCmxsPSB1bmlxdWUoZW5lcmd5JHNlYXNvbikNCmwgPSB1bmlxdWUoZW5lcmd5JHdlZWtkYXlzKQ0KDQpmb3IgKGkgaW4gbGwpew0KICBmb3IgKGogaW4gMDoyMyl7DQogICAgZm9yIChrIGluIDE6bGVuZ3RoKGwpKQ0KICAgIHsNCiAgICAgIGluZGV4ID0gd2hpY2goIGhvdXIoZW5lcmd5JERhdGVUaW1lKSA9PSBqICYgYXMuY2hhcmFjdGVyKGVuZXJneSR3ZWVrZGF5cyk9PSBsW2tdICYgYXMuY2hhcmFjdGVyKGVuZXJneSRzZWFzb24pPT1pICApDQogICAgICBmb3IgKG0gaW4gMzo3KQ0KICAgICAge2VuZXJneVtpbmRleCxtXSA9IGFzLm51bWVyaWMoaW1wdXRlKGVuZXJneVtpbmRleCxtXSwncmFuZG9tJykpfQ0KfX19DQoNCmBgYA0KDQojIyBGb3JjYXN0IGJ5IG1vbnRoDQoNCkFmdGVyIGhhdmUgZXhhbWluYXRlZCB0aGUgZGF0YSB3aXRoIGRpZmZlcmVudCBncmFudWxhcml0aWVzIChkYXlzLCB3ZWVrZGF5cyxldGMuKSwgd2UgaGF2ZSBkZWNpZGVkIHRvIGNvbmNlbnRyYXRlIGluIHByZWRpY3RpbmcgdGhlIGVuZXJneSBieSBtb250aC4gSW5kZWVkIHRoZXJlIGlzIGNsZWFybHkgYSBzZWFzb25hbGl0eSBpbiB0aGlzIGNhc2UuIEluIHRoZSBmb2xsb3dpbmcgY29kZSB3ZSBzaG93IHRoZSBmb3JlY2FzdCB3ZSBoYXZlIG9idGFpbmVkIGZvciB0aGUgZ2xvYmFsIGVuZXJneSB1c2luZyB0d28gd2VsbCBrbm93biBtZXRob2RzIGZvciB0aW1lIHNlcmllcyBmb3JlY2FzdGluZy4gDQoNCiMjIFByZXBhcmluZyB0aGUgdGltZXNlcmllDQoNClRvIHdvcmsgd2l0aCB0aW1lIHNlcmllcyBmb3JlY2FzdGluZywgZmlyc3Qgd2UgaGF2ZSB0byBjcmVhdGUgYSB0aW1lc2VyaWVzIG9iamVjdCB3aXRoIHRoZSByaWdodCBmcmVxdWVuY3kuIFRoZW4gd2UgbmVlZCB0byB0YWtlIGNhcmUgb2YgdGhlIGxhc3Qgb2JzZXJ2YXRpb24gKE5vdmVtYmVyIDIwMTApIHNpbmNlIHdlIGFyZSBtaXNzaW5nIDQgZGF5cyBvZiB0aGF0IG1vbnRoIGluIHRoZSBkYXRhLiBXZSBjb25zaWRlciB0aGVuIHRoZSBhdmVyYWdlIGRhaWx5IGNvbnN1bXB0aW9uIGR1cmluZyB0aGF0IG1vbnRoIGFuZCB3ZSBhZGQgdGhlICJtaXNzaW5nIGVuZXJneSIgdG8gdGhlIGxhc3QgdmFsdWUgb2YgdGhlIHRpbWUgc2VyaWUuIA0KDQpgYGB7cn0NCiMgQ3JlYXRlIGEgdGltZSBzZXJpZSBvYmplY3QNCg0KZW5lcmd5bW9udGhzID0gZGF0YS5mcmFtZShlbmVyZ3kgJT4lIG11dGF0ZShtb250aHllYXIgPSBmb3JtYXQobXlkYXRhJERhdGUsICIlWS0lbSIpKSAlPiUgc2VsZWN0KG1vbnRoeWVhciwgR2xvYmFsX2FjdGl2ZV9wb3dlcikgJT4lIGdyb3VwX2J5KG1vbnRoeWVhcikgJT4lIHN1bW1hcml6ZV9hbGwoc3VtKSkNCmVuZXJneW1vbnRocyA9IHRzKGVuZXJneW1vbnRoc1tjKDI6NDgpLDJdLCBzdGFydD0yMDA3LCBmcmVxdWVuY3kgPSAxMikNCg0KIyBBZGRpbmcgdGhlIGVuZXJneSBvZiA0IG1pc3NpbmcgZGF5cyBvZiBOb3ZlbWJlciAyMDEwDQoNCkRlY2VtYmVyMjAxMCA9IGVuZXJneSAlPiUgZmlsdGVyKHllYXIoRGF0ZSk9PSAyMDEwLCBtb250aChEYXRlKSA9PSAxMSkgJT4lIHNlbGVjdChEYXRlLCBHbG9iYWxfYWN0aXZlX3Bvd2VyKSAlPiUgZ3JvdXBfYnkoRGF0ZSkgJT4lICBzdW1tYXJpemVfYWxsKHN1bSkNCm1lYW5jb25zdW1wdGlvbiA9IG1lYW4oRGVjZW1iZXIyMDEwJEdsb2JhbF9hY3RpdmVfcG93ZXIpDQplbmVyZ3ltb250aHNbNDddID0gZW5lcmd5bW9udGhzWzQ3XSArIDQqbWVhbmNvbnN1bXB0aW9uDQpgYGANCg0KSW4gdGhlIGZvbGxvd2luZyBncmFwaCwgeW91IGNhbiBzZWUgdGhlIHRpbWUgc2VyaWUgd2UgYXJlIGFuYWx5c2luZy4gDQoNCmBgYHtyfQ0KYXV0b3Bsb3QoZW5lcmd5bW9udGhzKSt0aGVtZV9taW5pbWFsKCkNCmBgYA0KDQpXZSBjYW4gc2VlIHRoYXQgd2UgaGF2ZSB0d28gb3V0bGllcnMgaW4gdGhlIHRpbWUgc2VyaWVzLCByZXNwZWN0aXZlbHkgRGVjZW1iZXIgMjAwNyBhbmQgQXVndXN0IDIwMDguIFdlIGRlY2lkZSB0byBzdWJzdGl0dXRlIHRoZSB2YWx1ZSBvZiBBdWd1c3Qgd2l0aCB0aGUgbWVhbiBiZXR3ZWVuIHRoZSBwcmV2aW91cyBhbmQgZm9sbG93aW5nIEF1Z3VzdC4gV2Ugd2lsbCBsZWF2ZSBEZWNlbWJlciAyMDA3IGFzIGl0IGlzLCBzaW5jZSB3ZSBoYXZlIGp1c3QgdGhyZWUgb2JzZXJ2YXRpb25zIGluIHRoZSBzYW1lIG1vbnRoIChmb3IgQXVndXN0IDIwMDggd2UgaGF2ZSBmb3VyKS4NCg0KYGBge3J9DQojIFdlIGNoZWNrIHRoZSBvdXRsaWVycyBhbHNvIHdpdGggYW4gYXV0b21hdGljIGZ1bmN0aW9uDQpvdWxpZXJzPC0gdHNvdXRsaWVycyhlbmVyZ3ltb250aHMpIA0KDQojIFdlIHN1YnN0aXR1dGUgdGhlIEF1Z3VzdCAyMDA4DQplbmVyZ3ltb250aHNbMjBdID0gMC41KihlbmVyZ3ltb250aHNbOF0rZW5lcmd5bW9udGhzWzMyXSkNCmBgYA0KDQpXZSByZWNoZWNrIHRoZSB0aW1lIHNlcmllcyBvYnRhaW5lZC4NCg0KYGBge3J9DQphdXRvcGxvdChlbmVyZ3ltb250aHMpK3RoZW1lX21pbmltYWwoKQ0KYGBgDQoNCldlIGNyZWF0ZSB0aGUgdHJhbmluZyBhbmQgdGhlIHRlc3QuIEZvciB0aGUgdHJhaW5pbmcgd2Ugd2lsbCBjb25zaWRlciB0aGUgZmlyc3QgdGhyZWUgeWVhcnMgb2YgZGF0YSwgZm9yIHRoZSB0ZXN0IHRoZSBsYXN0IGVsZXZlbiBtb250aHMgb2YgdGhlIGxhc3QgeWVhci4NCg0KYGBge3J9DQojIENyZWF0ZSB0aGUgdHJhaW5pbmcgYW5kIHRoZSB0ZXN0DQp0cmFpbmluZyA9IHN1YnNldChlbmVyZ3ltb250aHMsIHN0YXJ0ID0gMSAsZW5kID0gMzYpDQp0ZXN0aW5nPSBzdWJzZXQoZW5lcmd5bW9udGhzLCBzdGFydD0gMzcsIGVuZD0gNDcpDQpgYGANCg0KTG9va2luZyBhdCB0aGUgdGltZSBzZXJpZSwgd2Uga25vdyB0aGF0IHRoZSBwcmVkaWN0aW9uIHdpbGwgYmUgYmFzZWQgb24gdGhlIHNlYXNvbmFsIGNvbXBvbmVudC4NCg0KDQojIyBFeHBvbmVudGlhbCBTbW9vdGhpbmcgbW9kZWxzDQoNCkZpcnN0IHdlIGxvb2sgYXQgYW4gZXhwb25lbnRpYWwgc21vb3RoaW5nIG1vZGVscyAoSG9sdC1XaW50ZXJzIG1vZGVscykuIEluIHRoZSBmb2xsb3dpbmcgaW1hZ2Ugd2UgY2FuIHNlZSB0aGUgcmVzdWx0cyB3ZSBvYnRhaW4gaW4gdGhlIHRyYWluaW5nIHVzaW5nIHRoZSBhdXRvbWF0aWMgc2VhcmNoLiBJbiBwYXJ0aWN1bGFyIHRoZSB2YWx1ZXMgZm9yIHRoZSBwYXJhbWV0ZXJzIGFscGhhIGFuZCBnYW1tYSwgYW5kIHRoZSBkaXN0cmlidXRpb24gb2YgdGhlIHJlc2lkdWFscy4gDQoNCmBgYHtyfQ0KIyBXZSBsb29rIGF0IHRoZSByZXN1bHRzIG9idGFpbmVkIGJ5IHRoZSBkZWZhdWx0IG1ldGhvZA0KZml0X2V0cz0gZXRzKHRyYWluaW5nKQ0Kc3VtbWFyeShmaXRfZXRzKQ0KYGBgDQpBcyBleHBlY3RlZCwgdGhlIG1vZGVsIHN1Z2dlc3RlZCBkb2Vzbid0IGhhdmUgYSB0cmVuZCBjb21wb25lbnQgYnV0IGp1c3QgYSBzZWFzb25hbCBvbmUgd2hpY2ggaXMgbXVsdGlwbGljYXRpdmUuDQpUaGUgc21hbGwgdmFsdWUgd2Ugb2J0YWluIGZvciB0aGUgc2Vhc29uYWxpdHkgbWVhbnMgdGhhdCB3ZSBnaXZlIGltcG9ydGFuY2UgdG8gYWxsIHRoZSBwcmV2aW91cyBvYnNlcnZhdGlvbiBkdXJpbmcgdGhlIHNhbWUgbW9udGguIFdlIGRvIHRoZSBmb2xsb3dpbmcgdGVzdCB0byBsb29rIGF0IHRoZSByZXNpZHVhbHMuDQoNCmBgYHtyfQ0KY2hlY2tyZXNpZHVhbHMoZml0X2V0cykNCmBgYA0KDQpUaGUgcC12YWx1ZSBpbiB0aGUgTGp1bmctQm94IHRlc3Qgc3VnZ2VzdCB0aGF0IHRoZSByZXNpZHVhbHMgYXJlIG5vdCB1bmNvcnJlbGF0ZS4gVGhpcyBtZWFucyB0aGF0IHRoZXJlIGNvdWxkIGJlIHNvbWUgaW5mb3JtYXRpb24gaW4gdGhlIHRpbWUgc2VyaWVzIHRoYXQgd2UgYXJlIG5vdCBjYXB0dXJpbmcuDQpGaW5hbGx5LCBXZSBsb29rIGF0IHRoZSBlcnJvciBvbiB0aGUgdGVzdC4NCg0KYGBge3J9DQpmb3JlY2FzdDEgPSBmb3JlY2FzdChmaXRfZXRzLGggPSAxMSkNCmExPSBhY2N1cmFjeShhcy5udW1lcmljKHRlc3RpbmcpLCBmb3JlY2FzdDEkbWVhbikNCnByaW50KGEyKQ0KYGBgDQoNCldlIHBsb3QgdGhlIGZvcmVjYXN0aW5nIGluIHRoZSB0ZXN0IHZlcnN1cyB0aGUgYWN0dWFsIHZhbHVlcy4gDQpgYGB7cn0NCmF1dG9wbG90KCBlbmVyZ3ltb250aHMpK2F1dG9sYXllcihmb3JlY2FzdDEsIFBJID0gRikrdGhlbWVfbWluaW1hbCgpDQpgYGANCldlIHRyeSB0byB1c2UgYWxzbyB0aGUgYWRkaXRpdmUgbWV0aG9kIGZvciB0aGUgc2Vhc29uYWxpdHksIHNvIHRoZSBtb2RlbCAoTSxOLEEpLg0KYGBge3J9DQpmaXRfZXRzX2FkZGl0aXZlID0gZXRzKHRyYWluaW5nLCBtb2RlbCA9ICJNTkEiKQ0Kc3VtbWFyeShmaXRfZXRzX2FkZGl0aXZlKQ0KYGBgDQoNCmBgYHtyfQ0KY2hlY2tyZXNpZHVhbHMoZml0X2V0c19hZGRpdGl2ZSkNCmBgYA0KDQpgYGB7cn0NCmZvcmVjYXN0MiA9IGZvcmVjYXN0KGZpdF9ldHNfYWRkaXRpdmUsaCA9IDExKQ0KYTI9IGFjY3VyYWN5KGFzLm51bWVyaWModGVzdGluZyksIGZvcmVjYXN0MiRtZWFuKQ0KcHJpbnQoYTIpDQpgYGANCg0KV2Ugb2J0YWluIHdvcnN0IHJlc3VsdHMgaW4gdGhlIHRlc3QgcmVzcGVjdCB0byB0aGUgcHJldmlvdXMgbW9kZWwsIGFzIG9uZSBjYW4gc2VlIGxvb2tpbmcgYXQgdGhlIFJNU0UsIE1BRSBhbmQgTUFQRS4NCg0KIyMgQVJJTUENCg0KV2UgZG8gYSBzaW1pbGFyIHN0dWR5IHdpdGggdGhlIEFSSU1BIG1vZGVsLiBXZSBmaXJzdCBsb29rIGF0IHRoZSBwYXJhbWV0ZXJzIGdpdmVuIGJ5IHRoZSBhdXRvbWF0aWMgZnVuY3Rpb24uDQoNCmBgYHtyfQ0KZml0LmFyaW1hID0gYXV0by5hcmltYSh0cmFpbmluZykNCnN1bW1hcnkoZml0LmFyaW1hKQ0KYGBgDQoNClRoZSBhcmltYSBtb2RlbCBmb3VuZCBpcyB0aGUgb25lIHRoYXQgY29uc2lkZXIganVzdCB0byBkaWZmZXJlbmNpYXRlIGJ5IHllYXIgKGxhZyAxMikuIEl0IG1lYW5zIHRoYXQgdGhlIGZvcmVjYXN0IHdpbGwgYmUgdGhlIG5haXZlIG9uZSwgY29uc2lkZXJpbmcgdGhlIG9ic2VydmF0aW9uIG9mIHRoZSBzYW1lIG1vbnRoIGluIHRoZSBwcmV2aW91cyB5ZWFyLg0KDQpXZSBzdHVkeSB0aGUgcmVzaWR1YWxzLg0KYGBge3J9DQpjaGVja3Jlc2lkdWFscyhmaXQuYXJpbWEpDQpgYGANClRoaXMgdGltZSB0aGUgdGVzdCBzdWdnZXN0cyB0aGF0IHRoZSByZXNpZHVhbHMgYXJlIG5vdCBjb3JyZWxhdGVkLg0KV2UgbG9vayBhdCB0aGUgZXJyb3Igb24gdGhlIHRlc3QuDQoNCmBgYHtyfQ0KZm9yZWNhc3QzID0gZm9yZWNhc3QoZml0LmFyaW1hLGggPSAxMSkNCmEzPSBhY2N1cmFjeShhcy5udW1lcmljKHRlc3RpbmcpLCBmb3JlY2FzdDIkbWVhbikNCnByaW50KGEzKQ0KYGBgDQoNCldlIHBsb3QgdGhlIGZvcmVjYXN0IGluIHRoZSB0ZXN0aW5nLCB0aGF0IGl0IGlzIHNpbXBseSB0aGUgcHJldmlvdXMgb2JzZXJ2YXRpb24gdGFrZW4gZHVyaW5nIHRoZSBzYW1lIG1vbnRoLiANCg0KYGBge3J9DQphdXRvcGxvdCggZW5lcmd5bW9udGhzKSthdXRvbGF5ZXIoZm9yZWNhc3QyLCBQSSA9IEYpK3RoZW1lX21pbmltYWwoKQ0KYGBgDQoNCg0KIyMgQ29uY2x1c2lvbg0KDQpDb21wYXJpbmcgdGhlIHJlc3VsdHMgd2UgaGF2ZSBvYnRhaW5lZCBvbiB0aGUgdGVzdCwgd2UgaGF2ZSBzZWVuIHRoYXQgdGhlIGJlc3QgbW9kZWwgaXMgRVRTKE0sTixNKS4gV2UgY2FuIHRoZW4gZm9yZWNhc3QgdXNpbmcgdGhpcyBtb2RlbCB0aGUgZW5lcmd5IGNvbnN1bXB0aW9uIG9mIHRoZSBmYW1pbHkgZm9yIDIwMTEuDQpgYGB7cn0NCm1vZGVsID0gZXRzKGVuZXJneW1vbnRocywgbW9kZWwgPSAiTU5NIikNCmZvcmVjYXN0NCA9IGZvcmVjYXN0KG1vZGVsLCBoID0gMTIpDQphdXRvcGxvdCggZW5lcmd5bW9udGhzKSthdXRvbGF5ZXIoZm9yZWNhc3Q0LCBQSSA9IFQpK3RoZW1lX21pbmltYWwoKQ0KDQpgYGANCg0K